package com.atguigu.gmall.cart.service.impl;

import com.atguigu.gmall.cart.mapper.CartInfoMapper;
import com.atguigu.gmall.cart.service.CartAsyncService;
import com.atguigu.gmall.cart.service.CartService;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.util.DateUtil;
import com.atguigu.gmall.model.cart.CarInfoVo;
import com.atguigu.gmall.model.cart.CartInfo;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.common.base.Predicates;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.security.Key;
import java.sql.Timestamp;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @author ：Shine
 * @description：
 * @date ：2021/2/26 9:53
 */
@Service
public class CartServiceImpl implements CartService {

    @Autowired
    private CartInfoMapper cartInfoMapper;

    @Autowired
    private ProductFeignClient productFeignClient;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private CartAsyncService cartAsyncService;

    @Override
    public void addToCart(Long skuId, String userId, Integer skuNum) {
        //   先查询该用户是否已经将该商品添加到购物车
        //    如果有就将商品数量添加，否则像数据库添加数据，最后将数据添加到Redis
        String cartKey = this.getCartKey(userId);
        //先判断缓存中是否有key
        if (!redisTemplate.hasKey(cartKey)) {
            //    从数据库查询数据并放入缓存
            this.loadCartCache(userId);
        }

        // QueryWrapper<CartInfo> cartInfoQueryWrapper = new QueryWrapper<>();
        // cartInfoQueryWrapper.eq("user_id", userId).eq("sku_id", skuId);
        // CartInfo cartInfoExist = cartInfoMapper.selectOne(cartInfoQueryWrapper);

        CartInfo cartInfoExist = (CartInfo) redisTemplate.opsForHash().get(cartKey, skuId.toString());
        if (cartInfoExist != null) {
            //    该商品已经存在，就相加数据，并且更新最近价格
            cartInfoExist.setSkuNum(cartInfoExist.getSkuNum() + skuNum);
            cartInfoExist.setSkuPrice(productFeignClient.getSkuPrice(skuId));

            //修改更新时间
            cartInfoExist.setUpdateTime(new Timestamp(new Date().getTime()));
            cartInfoExist.setIsChecked(1);

            // cartInfoMapper.updateById(cartInfoExist);现在开始使用异步方法
            cartAsyncService.updateCartInfo(cartInfoExist);

            //    操作缓存
        } else {
            CartInfo cartInfo = new CartInfo();

            SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
            cartInfo.setUserId(userId);
            cartInfo.setSkuId(skuId);
            //设置购物车的的价格
            cartInfo.setCartPrice(skuInfo.getPrice());
            //设置商品的实时价格
            cartInfo.setSkuPrice(skuInfo.getPrice());
            cartInfo.setSkuNum(skuNum);
            cartInfo.setImgUrl(skuInfo.getSkuDefaultImg());
            cartInfo.setSkuName(skuInfo.getSkuName());
            cartInfo.setIsChecked(1);
            cartInfo.setCreateTime(new Timestamp(new Date().getTime()));
            cartInfo.setUpdateTime(new Timestamp(new Date().getTime()));

            //cartInfoMapper.insert(cartInfo);
            cartAsyncService.saveCartInfo(cartInfo);

            // cartInfoExist这个在此处其实没用，相当于垃圾，但是地址还是存在的，这样写可以少写一次操作Redis的代码
            cartInfoExist = cartInfo;

        }

        //    操作缓存，向Redis存储数据，并且设置过期时间
        redisTemplate.opsForHash().put(cartKey, skuId.toString(), cartInfoExist);
        this.setCartKeyExpire(cartKey);
    }

    @Override
    public List<CartInfo> getCartInfoList(String userId, String userTempId) {
        List<CartInfo> cartInfoList = new ArrayList<>();
        //用户未登录
        if (StringUtils.isEmpty(userId)) {
            cartInfoList = this.getCartInfoList(userTempId);
        }

        //用户已登录
        /**
         * 1.根据skuId和userId进行合并操作
         * 2.更新之后要删除未登录的购物车数据，此时可以使用异步操作
         * 3.重新查一下购物车数据
         * 4.如果登录后购物车中每有数据，则直接合并数据
         */
        if (!StringUtils.isEmpty(userId)) {
            List<CartInfo> cartInfoNoLoginList = this.getCartInfoList(userId);

            if (!CollectionUtils.isEmpty(cartInfoNoLoginList)) {
                //合并购物车数据
                cartInfoList = this.mergeToCartInfo(cartInfoNoLoginList, userId);
                //    删除未登录的购物车数据
                this.deleteCartList(userTempId);
            }

            if (CollectionUtils.isEmpty(cartInfoNoLoginList) || StringUtils.isEmpty(userTempId)) {
                cartInfoList = this.getCartInfoList(userId);
            }
        }

        return cartInfoList;
    }

    @Override
    public List<CartInfo> getCartInfoList(String userId) {
        List<CartInfo> cartInfoList = new ArrayList<>();
        if (StringUtils.isEmpty(userId)) return cartInfoList;
        String cartKey = this.getCartKey(userId);
        cartInfoList = redisTemplate.opsForHash().values(cartKey);

        if (!CollectionUtils.isEmpty(cartInfoList)) {
            //    对添加到购物车的数据进行排序，以秒为单位
            cartInfoList.sort(new Comparator<CartInfo>() {
                @Override
                public int compare(CartInfo o1, CartInfo o2) {
                    //以秒为单位进行比较
                    return DateUtil.truncatedCompareTo(o2.getUpdateTime(), o1.getUpdateTime(), Calendar.SECOND);
                }
            });
            return cartInfoList;
        } else {
            //    当缓存中没有数据时，从数据库中获取数据
            cartInfoList = this.loadCartCache(userId);
            return cartInfoList;
        }
    }

    @Override
    public void checkCart(String userId, Long skuId, Integer isChecked) {
        //更新数据库信息
        cartAsyncService.checkCart(userId, skuId, isChecked);

        //更新缓存信息
        String cartKey = this.getCartKey(userId);
        // redisTemplate.opsForHash().get(cartKey,skuId.toString());
        BoundHashOperations boundHashOperations = redisTemplate.boundHashOps(cartKey);
        Boolean flag = boundHashOperations.hasKey(skuId.toString());
        if (flag) {
            CartInfo cartInfo = (CartInfo) boundHashOperations.get(skuId.toString());
            cartInfo.setIsChecked(isChecked);
            boundHashOperations.put(cartKey, cartInfo);

            //    设置购物车key过期时间
            this.setCartKeyExpire(cartKey);

        }

    }

    @Override
    public void deleteCart(String userId, Long skuId) {
        cartAsyncService.deleteCartInfo(userId, skuId);

        String cartKey = this.getCartKey(userId);
        BoundHashOperations boundHashOperations = redisTemplate.boundHashOps(cartKey);
        Boolean flag = boundHashOperations.hasKey(skuId.toString());
        if (flag) {
            boundHashOperations.delete(skuId.toString());
        }
    }

    @Override
    public List<CartInfo> getCartCheckedList(String userId) {
        String cartKey = this.getCartKey(userId);
        // List list = redisTemplate.opsForHash().values(cartKey);
        List<CartInfo> cartInfoList = redisTemplate.boundHashOps(cartKey).values();
/*
     List<CartInfo> cartInfoCheckedList = new ArrayList<>();
      if (!CollectionUtils.isEmpty(cartInfoList)) {
            for (CartInfo cartInfo : cartInfoList) {
                //    只有被选中的才添加到结算页面
                if (cartInfo.getIsChecked().intValue() == 1) {
                    cartInfoCheckedList.add(cartInfo);
                }

            }
        }*/
        //流式编程写法
        List<CartInfo> collect = cartInfoList.stream().filter((cartInfo -> {
            return cartInfo.getIsChecked().intValue() == 1;
        })).collect(Collectors.toList());

        return collect;
    }


    private List<CartInfo> mergeToCartInfo(List<CartInfo> cartInfoNoLoginList, String userId) {
        //    通过userId去查找购物车数据
        List<CartInfo> cartLoginInfoList = this.getCartInfoList(userId);
        //将购物车数据进行流式转换为map
        Map<Long, CartInfo> cartInfoLoginMap = cartLoginInfoList.stream().collect(Collectors.toMap(CartInfo::getSkuId, cartInfo -> cartInfo));
        //循环遍历map，如果skuId相同，就进行合并
        for (CartInfo cartInfoNoLogin : cartInfoNoLoginList) {
            Long skuId = cartInfoNoLogin.getSkuId();
            if (cartInfoLoginMap.containsKey(skuId)) {
                //    将两个购物车的skuNum相加，并且更新时间
                CartInfo loginCartInfo = cartInfoLoginMap.get(skuId);
                loginCartInfo.setSkuNum(loginCartInfo.getSkuNum() + cartInfoNoLogin.getSkuNum());
                loginCartInfo.setUpdateTime(new Timestamp(new Date().getTime()));

                //    按照未登录的选中状态进行设置
                if (cartInfoNoLogin.getIsChecked().intValue() == 1) {
                    loginCartInfo.setIsChecked(1);
                }

                //    将数据更新,必须使用同步更新数据
                QueryWrapper<CartInfo> cartInfoQueryWrapper = new QueryWrapper<>();
                cartInfoQueryWrapper.eq("user_id", userId).eq("sku_id", loginCartInfo.getSkuId());
                cartInfoMapper.update(loginCartInfo, cartInfoQueryWrapper);
            } else {
                //    未登录的时候是临时用户id，登录后需要将数据更新
                cartInfoNoLogin.setUserId(userId);
                cartInfoNoLogin.setCreateTime(new Timestamp(new Date().getTime()));
                cartInfoNoLogin.setUpdateTime(new Timestamp(new Date().getTime()));
                //    如果没有的话，直接向数据中添加数据
                // cartAsyncService.updateCartInfo(cartInfoNoLogin); 此时不可使用异步，会导致数据库没更新数据之前Redis就读取了数据
                cartInfoMapper.insert(cartInfoNoLogin);
            }

        }
        //    我们需要将合并后的数据信息进行返回，所以现在需要再次查询一次数据库
        List<CartInfo> cartInfoList = this.loadCartCache(userId);
        return cartInfoList;
    }

    private void deleteCartList(String userTempId) {
        //    删除购物车
        cartAsyncService.deleteCartInfo(userTempId);
        //    判断缓存中是否有数据
        String cartKey = this.getCartKey(userTempId);
        if (redisTemplate.hasKey(cartKey)) {
            redisTemplate.delete(cartKey);
        }
    }

    //根据用户id查询购物车列表，并将数据跟新到缓存中
    public List<CartInfo> loadCartCache(String userId) {
        List<CartInfo> cartInfoList = cartInfoMapper.selectList(new QueryWrapper<CartInfo>().eq("user_id", userId));
        if (CollectionUtils.isEmpty(cartInfoList)) return cartInfoList;

        String cartKey = this.getCartKey(userId);
        //遍历cartInfoList，向Redis中放入数据
        HashMap<String, CartInfo> cartInfoHashMap = new HashMap<>();
        for (CartInfo cartInfo : cartInfoList) {
            //商品价格可能已经跟新，此时需要更新一下商品价格
            cartInfo.setSkuPrice(productFeignClient.getSkuPrice(cartInfo.getSkuId()));
            // redisTemplate.opsForHash().put(cartKey,cartInfo.getSkuId(),cartInfo); //这个是单个设置
            cartInfoHashMap.put(cartInfo.getSkuId().toString(), cartInfo);
        }

        cartInfoList.sort(new Comparator<CartInfo>() {
            @Override
            public int compare(CartInfo o1, CartInfo o2) {
                return DateUtil.truncatedCompareTo(o2.getUpdateTime(), o1.getUpdateTime(), Calendar.SECOND);
            }
        });

        //一次性整体将数据存入Redis中
        redisTemplate.opsForHash().putAll(cartKey, cartInfoHashMap);

        //此时已经跟新Redis，需要执行跟新操作
        this.setCartKeyExpire(cartKey);

        return cartInfoList;
    }


    private void setCartKeyExpire(String cartKey) {
        redisTemplate.expire(cartKey, RedisConst.USER_CART_EXPIRE, TimeUnit.SECONDS);
    }

    private String getCartKey(String userId) {
        return RedisConst.USER_KEY_PREFIX + userId + RedisConst.USER_CART_KEY_SUFFIX;
    }
}
